Mutability¶

🎨 Indexing []¶

A review

In [2]:
fruits = ['plums', 'peaches', 'pears', 'persimmons']
fruits[0]
Out[2]:
'plums'
In [3]:
fruits[1]
Out[3]:
'peaches'
In [4]:
fruits[3]
Out[4]:
'persimmons'

NOTES

  • Indexing starts at 0
    • The first item is at [0]
    • The last item is $length - 1$
In [5]:
fruits[4]
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Input In [5], in <cell line: 1>()
----> 1 fruits[4]

IndexError: list index out of range

NOTES

  • Indexing beyond the final element gives you an IndexError
In [6]:
fruits = ['plums', 'peaches', 'pears', 'persimmons']
fruits[-1]
Out[6]:
'persimmons'
In [7]:
fruits[-3]
Out[7]:
'peaches'
In [8]:
fruits[-4]
Out[8]:
'plums'

NOTES

  • negative indexes work from the back of the list
  • $-1$ is the last element, etc.
In [9]:
fruits[len(fruits)-1], fruits[-1]
Out[9]:
('persimmons', 'persimmons')

NOTES

  • mnumonic: add the negative index to the length of the list to get the corresponding positive index
  • I usually only use negative indexes to retrieve the last value, or maybe second to last.

🎨 range¶

In [23]:
for i in range(5):
    print(i)
0
1
2
3
4

NOTES

  • What numbers were printed? (i.e. started with 0) What numbers were NOT printed? (i.e. 5)
In [10]:
range(5)
Out[10]:
range(0, 5)
In [24]:
list(range(5))
Out[24]:
[0, 1, 2, 3, 4]

NOTES

  • range returns...a range object.
    • a range object is iterable, like strings, lists, and files
  • So, it is not a list, but because it is iterable, you can turn it into a list
In [12]:
fruits = ['apples', 'apricots', 'avacados']
for index in range(len(fruits)):
    print(fruits[index])
    
print()

# Same as
for fruit in fruits:
    print(fruit)
apples
apricots
avacados

apples
apricots
avacados

NOTES

  • discuss this code before you run it
    • what numbers will range(len(fruits)) produce? Write them out.
    • why did we name those numbers index?
    • for each value of index, what does fruits[index] print?
In [27]:
list(range(4, 12))
Out[27]:
[4, 5, 6, 7, 8, 9, 10, 11]

NOTES

  • discuss this code before you run it: what do you think it should produce?
  • range(a, b) (two arguments) starts at a inclusive and goes to b exclusive.
In [43]:
list(range(4, 12, 2))
Out[43]:
[4, 6, 8, 10]

NOTES

  • discuss this code before you run it: what do you think it should produce?
  • range(a, b, c) (three arguments) starts at a inclusive and goes to b exclusive with a step size of c.

👨🏼‍🎨 Ranges¶

What python expression using range would you use to create the following sequences?

  1. 1, 2, 3, 4, 5
  2. 0, 2, 4, 6, 8
  3. -6, -3, 0, 3, 6
  4. 5, 4, 3, 2

NOTES

  • Work alone for 30 seconds and write down each expression
  • Then compare with a neighbor - do you have the same answers?
  • range(a, b, c) (three arguments) starts at a inclusive and goes to b exclusive with a step size of c.
In [44]:
list(range(1, 6))
Out[44]:
[1, 2, 3, 4, 5]
In [45]:
list(range(0, 10, 2))
Out[45]:
[0, 2, 4, 6, 8]
In [15]:
list(range(-6, 9, 3))
Out[15]:
[-6, -3, 0, 3, 6]
In [16]:
list(range(5, 1, -1))
Out[16]:
[5, 4, 3, 2]

🖌 🗡 List Mutability¶

In [48]:
bears = ['brown','black','polar','koala']
bears
Out[48]:
['brown', 'black', 'polar', 'koala']
In [49]:
bears[3]
Out[49]:
'koala'
In [50]:
bears[3] = 'panda'
In [51]:
bears[3]
Out[51]:
'panda'
In [52]:
bears
Out[52]:
['brown', 'black', 'polar', 'panda']

NOTES

  • Lists are mutable
    • not only can we make them longer with .append(), we can change the value a given index points to
  • Draw this out
    • original values on the heap
    • a list on the heap with 4 slots pointing to the original values
    • a new value ('panda') on the heap and the last slot now pointing to it
    • 'koala' is still on the heap, just nothing is pointing to it at the moment
  • we can change an item in a list just like we would change a variable.
    • the value itself doesn't change, but our reference changes to point to a different value

Lists and Functions¶

How do these functions compare?

In [23]:
def large_value_of_two(items):
    """Replace 2 with something bigger (like 5)"""
    new_items = []
    for item in items:
        if item == 2:
            item = 5
        new_items.append(item)
    return new_items
In [24]:
def large_value_of_2(items):
    """Replace 2 with something bigger (like 5)"""
    for i in range(len(items)):
        if items[i] == 2:
            items[i] = 5
In [26]:
numbers = [1, 2, 3, 4]

print("Numbers before:", numbers)
first_return = large_value_of_two(numbers)
print("Numbers after:", numbers)
print("large_value_of_two returned:", first_return)
print()

print("Numbers before:", numbers)
second_return = large_value_of_2(numbers)
print("Numbers after:", numbers)
print("large_value_of_2 returned:", second_return)
Numbers before: [1, 2, 3, 4]
Numbers after: [1, 2, 3, 4]
large_value_of_two returned: [1, 5, 3, 4]

Numbers before: [1, 2, 3, 4]
Numbers after: [1, 5, 3, 4]
large_value_of_2 returned: None
Because lists are mutable, anyone with a reference to the list has the ability to change it's contents.

Just because a function can change a list doesn't mean it should.

Prefer immutability whenever possible: only change inputs when there is a clear advantage over making a copy

Clearly define and document the intent of your functions.

  • does a function change the input or only read the input?

edits.py¶

score_board.py¶

Key Ideas¶

  • indexing lists with []
  • range
  • list mutability